Skip to content

Refactor(Phase 6): 유저 플로우 개선 — Auth + Trip UX/접근성 완성#8

Merged
mayrang merged 8 commits intomainfrom
refactor/phase-6-auth-ux
Mar 30, 2026
Merged

Refactor(Phase 6): 유저 플로우 개선 — Auth + Trip UX/접근성 완성#8
mayrang merged 8 commits intomainfrom
refactor/phase-6-auth-ux

Conversation

@mayrang
Copy link
Copy Markdown
Owner

@mayrang mayrang commented Mar 30, 2026

Summary

Phase 5 (Tailwind/Emotion 완전 제거) 이후 실제 유저 플로우를 대상으로 E2E 테스트 구축, UX 개선, 성능 최적화, 접근성 완성까지 수행한 Phase 6 전체 작업.

  • E2E 테스트 인프라: MSW Express 서버(stateful) + Playwright dual webServer — 백엔드 없이 Auth/Trip/Enrollment 플로우 전부 테스트 가능
  • 폼 마이그레이션: Auth 폼 9개 → react-hook-form + zod (zodResolver 직접 구현, 네트워크 차단 환경)
  • 성능 최적화: FCP 4.7s → 0.8s (next/font 적용, AppShell dynamic import 분리, Maps/GTM 중복 제거)
  • 에러 핸들링: ErrorPolicy + createMutationOptions 내부 라이브러리 설계 + TDD 45개 (Auth/Trip 전 mutation 적용)
  • Trip UX: Auth Race Condition 수정 (isAuthResolved guard), loginPath redirect 버그 수정
  • 접근성 완성: Production Lighthouse A11y 79/72/82 → 100/100/100

최종 수치

페이지 Performance Accessibility FCP
/ 89 100 213ms
/trip/list 76 100 336ms
/trip/detail/1 89 100 213ms
/login 75 (+12) 100 0.8s
/registerEmail 75 (+19) 100 0.8s

주요 변경 영역

신규 파일 / 인프라

  • src/mocks/db/store.ts + src/mocks/routes/ — stateful MSW in-memory DB (1533줄)
  • src/shared/lib/errors/ — ErrorPolicy 에러 핸들링 라이브러리 5개 파일
  • src/shared/lib/zodResolver.ts — RHF resolver 직접 구현
  • src/shared/lib/registerDraft.ts — 이메일 임시 저장 (TTL 1시간)
  • src/shared/hooks/useNfcField.ts — 한글 NFC 정규화 + 디바운싱
  • e2e/auth.spec.ts, e2e/trip.spec.ts, e2e/tripDetail.spec.ts, e2e/enrollment.spec.ts

접근성 수정 (A11y 100점)

  • AppShell.tsx, Layout.tsx<div><main> (landmark-one-main)
  • globals.css--color-keycolor #3e8d00→#2d7a00 (color-contrast)
  • Navbar.tsx — inactive 탭 1.58:1→5.24:1 (color-contrast)
  • ShareIcon.tsx — 중첩 버튼 구조 해소 (button-name/target-size)
  • 기타 button-name, document-title, label-content-name-mismatch 수정

주요 버그 수정

  • axiosInstance — 401 로그인 실패 시 refresh 무한 시도 버그
  • ValidationInputField — forwardRef 미적용으로 RHF ref drop 버그
  • RegisterDone — 회원가입 완료 후 /login redirect (이미 로그인 상태인데 재로그인 요구)
  • useAuth.loginPathremoveItemgetItem 순서 오류
  • TripDetailHeader auth race condition — 호스트 버튼 깜빡임

Test plan

  • yarn build && yarn start → Lighthouse 전 페이지 A11y 100 확인
  • yarn test → Vitest 전체 통과 (273개+)
  • yarn test:e2e — auth 34개, trip/tripDetail/enrollment 19개 통과
  • /login FCP ≤ 1s 확인
  • 회원가입 전체 플로우 (RegisterEmail → RegisterDone → /) 확인
  • TripDetail 호스트 직접 URL 접근 시 버튼 깜빡임 없음 확인

참조

🤖 Generated with Claude Code

mayrang and others added 7 commits March 30, 2026 00:00
## 주요 변경사항

### Bug Fix
- TripDetailHeader: 호스트 버튼 직접 접근 시 깜빡임 버그 수정
  - 서버 프리페치(null 토큰) → AppShell 토큰 복구 전 hostUserCheck=false 덮어쓰기 문제
  - isAuthResolved(!!accessToken || isGuestUser) guard 추가
  - isGuestUser 변수 섀도잉 버그 수정 (isGuestUser: isGuestUserStore 별칭)
- useAuth: loginPath redirect 미동작 버그 수정
  - localStorage.getItem("loginPath") 읽기 전 removeItem 하던 문제

### Error Handling
- useTripDetail: createMutationOptions 적용 (update/delete mutation)
- useEnrollment: cancelMutation에 createMutationOptions 적용

### Accessibility (WCAG 2.1 AA)
- TripDetailPage: 스켈레톤 UI 추가 (auth 복구 대기 중 animate-pulse)
- TripDetailPage: 스크롤 컨테이너 role="region" + tabIndex=0 (scrollable-region-focusable 해결)
- TripDetailPage: 동행자 행 div→button 전환 + aria-label
- TripDetailPage: 댓글 FAB div→button 전환 + aria-label
- TripDetailHeader: 알림/공유/더보기 div→button 전환 + aria-label
- ApplyListButton: 북마크 버튼 aria-label 추가 (button-name critical 위반 해결)

### E2E / MSW
- tripDetail.spec.ts: 호스트 버튼 + 삭제 + axe 측정 스펙 작성
- enrollment.spec.ts, trip.spec.ts: MSW stateful mock 기반 재작성
- MSW routes 확장 (trip, misc)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## 변경 내용

### 접근성 (axe WCAG 2 AA)
- globals.css: --color-text-muted #848484(3.67:1) → #6b6b6b(5.24:1), --color-text-muted2 #ababab(2.25:1) → #717171(4.80:1)
- EmailLoginForm: '처음 오셨나요?' inline #848484 → var(--color-text-muted) 클래스로 전환
- InfoText: 기본 색상 #ABABAB 하드코딩 → var(--color-text-muted2) CSS 변수 참조
- auth E2E axe 검사에 .exclude('nextjs-portal') 추가 (dev overlay 뱃지 제외)

### react-hook-form ref 연결 수정
- ValidationInputField: forwardRef 미적용으로 RHF ref가 DOM input에 미연결되어
  getValues().email = undefined → zod "Required" 에러 발생하던 구조적 버그 수정
- forwardRef + onBlur prop 추가, ref를 StateInputField → <input> 까지 전달

### E2E 테스트
- fillReactInput 헬퍼: nativeInputValueSetter 우회 → locator.fill() 으로 단순화
- '로그인' 버튼 locator에 exact: true 추가 (소셜 로그인 버튼 strict mode 위반 수정)
- 결과: 33 passed, axe 전 페이지 "위반 없음 ✓"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## 변경 내용

### RegisterDone 자동 로그인
- 회원가입 완료 시 /login 리다이렉트 → / 홈으로 변경
- /api/users/sign-up 응답에 accessToken이 포함되어 있어 이미 로그인 상태임이 확인됨
  (useAuth.registerEmailMutation.onSuccess에서 setLoginData 호출)
- 백엔드 협의 불필요 — MSW 검증으로 확인 완료

### MSW 서버 테스트 격리
- db/store.ts: db.reset() 메서드 추가 (모든 데이터 초기화 + 시드 재등록)
- http.ts: POST /api/test/reset 엔드포인트 추가
- auth.spec.ts: 이메일 회원가입 beforeEach db 리셋 추가

### E2E 테스트 안정화
- auth.spec.ts: test.describe.configure({ mode: 'serial' }) 추가
  — fullyParallel: true 환경에서 공유 MSW db 상태 충돌 방지
- goToVerifyEmail/goToRegisterPassword: email 파라미터 추가 (기본값 유지)
- goToRegisterDone: 고유 이메일(reg{timestamp}@test.com) 사용 — new@test.com 중복 방지
- 전체 플로우 E2E 테스트 추가: RegisterEmail → RegisterDone → / 이동 확인
- 결과: 34 passed, 0 failed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## 변경 내용

### landmark-one-main (전 페이지 공통)
- AppShell.tsx: 내부 콘텐츠 래퍼 <div> → <main>
- Layout.tsx: auth route 내부 <div> → <main>
- 앱 전체에 <main> 랜드마크가 없던 구조적 문제 해소

### color-contrast (WCAG 2.1 AA 4.5:1)
- globals.css: --color-keycolor #3e8d00(4.11:1) → #2d7a00(5.41:1)
- BoxLayoutTag: 하드코딩 rgba(132,132,132,1) → var(--color-text-muted)
- Navbar: inactiveColor var(--color-muted3)(1.58:1) → var(--color-text-muted)(5.24:1)
- TripListPage: text-[#3e8d00] → text-[var(--color-keycolor)]

### button-name / target-size
- ShareIcon: className/ariaLabel prop 추가, inner button에 직접 적용
- TripDetailHeader: 중첩 button wrapper 제거, width: "auto" 고정값 제거

### label-content-name-mismatch (WCAG 2.5.3)
- TripDetailPage: 동행자 버튼 aria-label 제거 (visible text로 충분)

### document-title
- /trip/list/page.tsx: metadata 추가
- /trip/apply/[travelNumber]/page.tsx: metadata 추가

### button-name (기타)
- Header: 뒤로가기 버튼 aria-label="뒤로 가기" 추가
- CreateTripButton: AddIcon aria-label + aria-expanded 추가
- TripListPage: AlarmIcon div → button 전환 + aria-label

### 문서화
- phase-6.md: §13 추가 (배경/위반 분석/수정 목록/수치/트러블슈팅/회고)
- progress.md: Phase 6 완료 처리, 최종 수치 반영 (A11y 100/100/100)

## 수치
- A11y: / 79→100, /trip/list 72→100, /trip/detail 82→100
- Performance 유지: / 89, /trip/list 76, /trip/detail 89
- FCP 유지: 213ms / 336ms / 213ms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix(a11y): Production Lighthouse 기반 전체 페이지 접근성 완성 — A11y 100점
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
release-back Ready Ready Preview, Comment Mar 30, 2026 7:53am
release-test Ready Ready Preview, Comment Mar 30, 2026 7:53am

@mayrang
Copy link
Copy Markdown
Owner Author

mayrang commented Mar 30, 2026

코드 리뷰 — Phase 6 전체

Blocking (머지 전 수정 필요)

1. axiosInstance 인터셉터 — 로그인 엔드포인트 예외 처리 미완료

// 현재: /api/login 401 → 인터셉터 → refresh 시도
// 문제: 잘못된 비밀번호 입력 시 "서버 오류" Toast가 뜨는 버그 존재

// phase-6.md §10 트러블슈팅에서 인지된 이슈:
// "근본 해결: /api/login을 인터셉터의 예외 경로로 추가해야 한다. 현재는 범위 밖."

E2E 테스트에서는 /api/token/refresh를 400으로 강제해 우회하는 방식으로 검증했지만, 실제 사용자 환경에서는 버그가 재현된다. Phase 6 범위 내 수정이 필요한지 판단 필요.

→ 처리 방안: axiosInstance의 인터셉터에 /api/login 경로 예외 추가하거나, 다음 Phase에서 처리할 known issue로 명시.


Non-blocking (권장 개선)

2. createMutationOptionsonBusinessError 타입 제약

// 현재
onBusinessError?: (error: unknown, variables: unknown) => void;

// 권장: 제네릭으로 variables 타입 추론
createMutationOptions<TVariables>({
  onBusinessError?: (error: unknown, variables: TVariables) => void;
})

현재 variablesunknown이라 콜백 내부에서 타입 단언이 필요하다. 사용 빈도가 높아지면 개선 고려.


3. MSW stateful DB — 테스트 격리 패턴 문서화

// auth.spec.ts
test.describe.configure({ mode: 'serial' });
// beforeEach: POST /api/test/reset

serial + reset 패턴이 auth.spec.ts에만 적용됐고 다른 spec 파일에는 없다. trip/enrollment spec도 공유 DB를 사용할 경우 같은 문제가 생길 수 있음. 패턴을 Playwright 글로벌 fixture로 추출하거나, 각 spec에 동일하게 적용하는 것을 권장.


4. AppShell.tsx<main> skip navigation 타겟

// 현재
<main className="relative h-full ...">

// 권장
<main id="main-content" className="relative h-full ...">

"콘텐츠로 바로가기" skip link를 달 때 id가 없으면 연결 불가. 지금 달지 않더라도 id를 미리 추가해두면 추후 비용 없음.


5. RegisterDraft — sessionStorage 중복 관리

현재:
- RegisterEmail: sessionStorage.setItem('sessionToken')  ← 인증 흐름
- registerDraft.ts: localStorage.setItem('register_draft') ← 이메일 임시 저장

두 storage 키가 다른 파일에서 별도로 관리된다. registerDraft.ts에 sessionToken 관련 유틸도 통합하거나, storage 키 상수를 한 곳에서 관리하는 것을 권장.


잘된 점

  • ErrorPolicy 설계: network / business / system 3분류 + 콜백 위임 패턴이 명확하다. onBusinessError가 항상 caller로 위임되는 원칙 덕분에 공통 라이브러리가 폼 레벨 UX에 간섭하지 않는다.
  • isAuthResolved guard: 서버 프리페치(accessToken=null) → 클라이언트 Zustand 복구 타이밍 차이를 명시적 플래그로 해결한 접근이 깔끔하다. 근본 해결(httpOnly 쿠키 전환)에 대한 주석도 미래 작업자를 위해 유용하다.
  • E2E 테스트 loginAndNavigateToTripDetail(): 서버/클라이언트 렌더링 차이 때문에 page.goto() 직접 사용이 불가한 이유가 주석으로 명확히 문서화됐다. 나중에 "왜 이렇게 복잡하게 했지?"라고 단순화하는 실수를 방지한다.
  • Production 빌드 기반 Lighthouse: dev 서버 수치를 신뢰하지 않고 yarn build && yarn start 기준으로 측정한 원칙이 옳다.

Reviewer: Claude Sonnet 4.6

AppShell의 <main>에 id 추가. 추후 "콘텐츠로 바로가기"
skip navigation 링크(#main-content) 연결 시 별도 수정 불필요.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@mayrang
Copy link
Copy Markdown
Owner Author

mayrang commented Mar 30, 2026

리뷰 반영

Blocking #1 — axiosInstance 로그인 엔드포인트 예외 처리

재확인 결과 이미 9ea6fdb8 커밋에서 수정 완료된 상태였습니다.

// src/shared/api/axiosInstance.ts
const isLoginEndpoint = originalRequest?.url?.includes('/api/login');
if ((error.response?.status === 401 || error.response?.status === 403) && !isLoginEndpoint) {
  // refresh 시도 — 로그인 엔드포인트는 진입하지 않음

docs/phase-6.md의 "현재는 범위 밖" 주석이 코드 수정 이전 시점에 작성된 것이었고, 코드 확인 없이 리뷰에 블로킹으로 올린 오류였습니다. 블로킹 없음 ✅


Non-blocking #4<main id="main-content"> 추가

커밋 4a7b7f44 에서 반영했습니다.

// AppShell.tsx
<main
  id="main-content"
  className="relative h-full ..."
>

@mayrang mayrang merged commit 4b934e2 into main Mar 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant